package com.fluidops.datacatalog.reltables.adapter;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;

import com.fluidops.datacatalog.reltables.data.Vocabulary.DCTERMS;
import com.fluidops.datacatalog.reltables.data.Vocabulary.DataCube;
import com.fluidops.datacatalog.reltables.data.Vocabulary.SDMX;
import com.fluidops.datacatalog.reltables.data.Vocabulary.SKOS;
import com.fluidops.datacatalog.reltables.data.Vocabulary.WorldBank;
import com.fluidops.datacatalog.reltables.util.LRUCache;

public class WorldbankAdapter implements ExtractionAdapter{

	private static final Logger logger = Logger.getLogger(WorldbankAdapter.class.getName());

	private RepositoryConnection schemaConnection;
	private RepositoryConnection dataConnection;

	private URI schemaContext;
	private URI dataContext;

	public static final int CACHE_SIZE = 300;

	private Map<URI, Set<String>> entityLabelCache;

	public WorldbankAdapter(RepositoryConnection schemaConnection,
			RepositoryConnection dataConnection, URI schemaContext,
			URI dataContext) {
		this.schemaConnection = schemaConnection;
		this.dataConnection = dataConnection;
		this.schemaContext = schemaContext;
		this.dataContext = dataContext;
		/*
		 * several caches to avoid to much database accesses
		 */
		this.entityLabelCache = new LRUCache<URI, Set<String>>(CACHE_SIZE);
	}

	@Override
	public Set<String> retrieveDatasetStringLabels(URI identifier) {
		Set<String> dsLabels = new TreeSet<String>();

		ValueFactory vf = this.schemaConnection.getValueFactory();
		URI datasetSubject = vf.createURI("http://worldbank.270a.info/classification/indicator/", identifier.getLocalName());

		RepositoryResult<Statement> result = null;
		try{
			try{
				result = this.schemaConnection.getStatements(datasetSubject, DCTERMS.TITLE, null, false, this.schemaContext);

				Statement s;
				while(result.hasNext()){
					s = result.next();
					dsLabels.add(s.getObject().stringValue());
				}
			}finally{
				if(result != null){
					result.close();
				}
			}
		}catch(RepositoryException e){
			logger.warn("Could not query repository for dataset string labels " + identifier.stringValue() , e);
		}

		return dsLabels;
	}

	@Override
	public List<URI> retrieveEntityURIs(URI datasetIdentifier) {
		List<URI> entities = new LinkedList<URI>();

		StringBuilder query = new StringBuilder();
		query.append("SELECT DISTINCT ?entity ");

		ValueFactory vf = this.dataConnection.getValueFactory();
		URI datasetObject = vf.createURI("http://worldbank.270a.info/classification/indicator/", datasetIdentifier.getLocalName());

		String from = "";
		if(this.dataContext != null){
			from = "FROM <" + this.dataContext.stringValue() + ">";
		}
		query.append(from);
		query.append(" WHERE { ");
		query.append(" ?obs <").append(WorldBank.INDICATOR.stringValue()).append("> <").append(datasetObject.stringValue()).append("> ; ");
		query.append(" <").append(SDMX.REF_AREA).append("> ?entity . } ");

		try {
			TupleQuery tquery = this.dataConnection.prepareTupleQuery(QueryLanguage.SPARQL, query.toString());
			TupleQueryResult result = null;
			try{
				result = tquery.evaluate();
				BindingSet bs;
				while(result.hasNext()){
					bs = result.next();
					entities.add((URI) bs.getValue("entity"));
				}
			}finally{
				if(result != null){
					result.close();
				}
			}
		} catch (RepositoryException e) {
			logger.warn("Could not access repository when querying for entity URIs of " + datasetIdentifier.stringValue(), e);
		} catch (MalformedQueryException e) {
			logger.warn("Malformed query when querying entity URIs of " + datasetIdentifier.stringValue(), e);
		}catch (QueryEvaluationException e) {
			logger.warn("Could not evaluate query when querying entity URIs of " + datasetIdentifier.stringValue(), e);
		}

		return entities;
	}

	@Override
	public Set<String> retrieveEntityStringLabels(final URI entityURI) {
		if(!this.entityLabelCache.containsKey(entityURI)){
			Set<String> entityLabels = new TreeSet<String>();

			StringBuilder query = new StringBuilder();
			query.append("SELECT DISTINCT ?label ");

			String from = "";
			if(this.schemaContext != null){
				from = "FROM <" + this.schemaContext.stringValue() + ">";
			}
			query.append(from);
			query.append(" WHERE { ");
			query.append(" <").append(entityURI.stringValue()).append("> <").append(SKOS.PREFLABEL).append(">  ?label . } ");

			try {
				TupleQuery tquery = this.schemaConnection.prepareTupleQuery(QueryLanguage.SPARQL, query.toString());
				TupleQueryResult result = null;
				try{
					result = tquery.evaluate();
					BindingSet bs;
					while(result.hasNext()){
						bs = result.next();
						if(bs.hasBinding("label")){
							entityLabels.add( bs.getValue("label").stringValue());
						}
					}
					this.entityLabelCache.put(entityURI, entityLabels);
				}finally{
					if(result != null){
						result.close();
					}
				}

			} catch (RepositoryException e) {
				logger.warn("Could not access repository when querying for entity labels of " + entityURI.stringValue(), e);
			} catch (MalformedQueryException e) {
				logger.warn("Malformed query when querying entity labels of (possibly invalid URI)" + entityURI.stringValue());
			}catch (QueryEvaluationException e) {
				logger.warn("Could not evaluate query when querying entity labels of " + entityURI.stringValue(), e);
			}
		}
		return this.entityLabelCache.get(entityURI);
	}

	@Override
	public List<URI> retrieveDimensionURIs(URI datasetIdentifier) {
		List<URI> dimensions = new LinkedList<URI>();
		ValueFactory vf = this.schemaConnection.getValueFactory();
		URI datasetSubject = vf.createURI("http://worldbank.270a.info/classification/indicator/", datasetIdentifier.getLocalName());
		RepositoryResult<Statement> result = null;
		try{
			try{
				result = this.schemaConnection.getStatements(datasetSubject, DataCube.DIMENSION, null, false, this.schemaContext);

				Statement s;
				while(result.hasNext()){
					s = result.next();
					if(!s.getObject().equals(SDMX.REF_AREA)){
						dimensions.add((URI) s.getObject());
					}
				}
			}finally{
				if(result != null){
					result.close();
				}
			}
		}catch(RepositoryException e){
			logger.warn("Could not query repository for dimension uris " + datasetIdentifier.stringValue() , e);
		}

		return dimensions;
	}

	@Override
	public Set<String> retrieveDimensionStringLabels(URI datasetIdentifier, URI dimensions) {
		Set<String> dimLabels = new TreeSet<String>();

		RepositoryResult<Statement> result = null;
		try{
			try{
				result = this.schemaConnection.getStatements(dimensions, SKOS.PREFLABEL, null, false, this.schemaContext);

				Statement s;
				while(result.hasNext()){
					s = result.next();
					dimLabels.add(s.getObject().stringValue());
				}
			}finally{
				if(result != null){
					result.close();
				}
			}
		}catch(RepositoryException e){
			logger.warn("Could not query repository for dimension labels of " + dimensions.stringValue() , e);
		}
		return dimLabels;
	}

	@Override
	public Set<String> retrieveDimensionStringLabels(URI datasetIdentifier, List<URI> dimensions) {
		TreeSet<String> dimLabels = new TreeSet<String>();
		for(URI dimension : dimensions){
			dimLabels.addAll(retrieveDimensionStringLabels(datasetIdentifier, dimension));
		}
		return dimLabels;
	}

	@Override
	public Set<String> retrieveDimensionValueStringLabels(URI datasetIdentifier, URI dimension) {
		Set<String> dimValLabels = new TreeSet<String>();

		ValueFactory vf = this.schemaConnection.getValueFactory();
		URI datasetSubject = vf.createURI("http://worldbank.270a.info/classification/indicator/", datasetIdentifier.getLocalName());

		RepositoryResult<Statement> result = null;
		try{
			try{
				result = this.schemaConnection.getStatements(datasetSubject, DCTERMS.TITLE, null, false, this.schemaContext);

				Statement s;
				while(result.hasNext()){
					s = result.next();
					dimValLabels.add(s.getObject().stringValue());
				}
			}finally{
				if(result != null){
					result.close();
				}
			}
		}catch(RepositoryException e){
			logger.warn("Could not query repository for dimension labels of " + datasetIdentifier.stringValue() , e);
		}

		Set<String> tempSet = new TreeSet<String>();
		for(String title : dimValLabels){
			tempSet.addAll(titleTokens(title));
		}
		tempSet.addAll(dimValLabels);

		return tempSet;
	}

	private Set<String> titleTokens(String title){
		Set<String> titleTokens = new TreeSet<String>();
		Set<String> brackets = new TreeSet<String>();

		StringBuilder sb = new StringBuilder(title);

		int openIndex = 0;
		int closeIndex;
		while(openIndex != -1){
			openIndex = sb.indexOf("(");

			if(openIndex != -1){
				closeIndex = sb.indexOf(")", openIndex);

				brackets.add(sb.substring(openIndex + 1, closeIndex));
				sb.delete(openIndex, closeIndex + 1);
			}

		}

		for(String s : sb.toString().split(",")){
			titleTokens.add(s.trim());
		}

		for(String s : brackets){
			for(String sep : s.split(",")){
				titleTokens.add(sep.trim());
			}
		}

		return titleTokens;
	}



	@Override
	public Set<String> retrieveDimensionValueStringLabels(URI datasetIdentifier, List<URI> dimensions) {
		TreeSet<String> dimValLabels = new TreeSet<String>();
		if(!dimensions.isEmpty()){
			dimValLabels.addAll(retrieveDimensionValueStringLabels(datasetIdentifier, dimensions.get(0)));
		}
		return dimValLabels;
	}

	@Override
	public void close() {
		try{
			if(this.schemaConnection.isOpen()){
				this.schemaConnection.close();
			}
		}catch(RepositoryException e){
			logger.warn("Could not close schema connection ", e);
		}

		try{
			if(this.dataConnection.isOpen()){
				this.dataConnection.close();
			}
		}catch(RepositoryException e){
			logger.warn("Could not close data connection ", e);
		}

	}

}
